﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace TtfLibrary
{
    public class StructureWriter
    {
        public Stream Stream;

        public Stack<long> PaddingStack = new Stack<long>();

        public void StartRegion()
        {
            PaddingStack.Push(Stream.Position);
        }

        public class FileRegion
        {
            public Stream Stream;

            public long StartPosition;
            public long EndPosition;
            public long Length { get { return EndPosition - StartPosition; } }

            public byte[] GetContent()
            {
                var originalPos = Stream.Position;

                var result = new byte[Length];
                Stream.Position = StartPosition;
                Stream.Read(result, 0, result.Length);

                Stream.Position = originalPos;
                return result;
            }

            public void Padding(int modulus = 4)
            {
                var padding = Length % modulus;
                if (padding != 0)
                {
                    var pad = new byte[modulus - padding];
                    for (var i = 0; i < pad.Length; i++)
                        pad[i] = 0;
                    this.Stream.Write(pad, 0, pad.Length);
                }
            }

            public UInt32 CalculateChecksum()
            {
                return TtfFileWriter.CalculateChecksum(GetContent());
            }
        }

        public FileRegion EndRegion()
        {
            return new FileRegion() { Stream = Stream, StartPosition = PaddingStack.Pop(), EndPosition = Stream.Position };
        }

        public void WriteObject(object obj, string[] parts = null)
        {
            if (WriteScalarType(obj))
                return;

            var objType = obj.GetType();
            var fields = objType.GetFields();

            string currentPartName = null;
            foreach (var field in fields)
            {
                if (field.IsStatic)
                    continue;

                var fieldType = field.FieldType;

                if (field.GetCustomAttributes(typeof(TtfStopAttribute), false).Length != 0)
                    break;

                var part = field.GetCustomAttributes(typeof(TtfPartAttribute), false).FirstOrDefault() as TtfPartAttribute;
                if (part != null)
                    currentPartName = part.Name;
                if (parts != null && !parts.Contains(currentPartName) || parts == null && currentPartName != null)
                    continue;

                var value = field.GetValue(obj);
                if (WriteScalarType(value))
                {
                }
                else if (fieldType.GetCustomAttributes(typeof(TtfStructureAttribute), false).Length != 0)
                {
                    WriteObject(value);
                }
                else if (value is IEnumerable)
                {
                    foreach (var item in (IEnumerable)value)
                        WriteObject(item);
                }
                else
                {
                    Console.WriteLine(String.Format("Unknown field type: {0}", field.FieldType.Name));
                    break;
                }
            }

            if (parts == null && obj is IAfterWrite)
                ((IAfterWrite)obj).AfterWrite(this);
        }

        public bool WriteScalarType(object obj)
        {
            var type = obj.GetType();
            if (type == typeof(UInt16)) WriteValue(2, (UInt16)obj);
            else if (type == typeof(UInt32)) WriteValue(4, (UInt32)obj);
            else if (type == typeof(UInt64)) WriteValue(8, (UInt64)obj);
            else if (type == typeof(Int16)) WriteValue(2, (UInt64)(Int16)obj);
            else if (type == typeof(Int32)) WriteValue(4, (UInt64)(Int32)obj);
            else if (type == typeof(Int64)) WriteValue(8, (UInt64)(Int64)obj);
            else if (type == typeof(byte)) WriteValue(1, (byte)obj);
            else if (type == typeof(char)) WriteValue(1, (char)obj);
            else if (type == typeof(string)) WriteString((string)obj);
            else
                return false;
            return true;
        }

        public void WriteString(string value)
        {
            WriteBytes(Encoding.Default.GetBytes(value));
            WriteBytes(new byte[] { 0 });
        }

        public void WriteValue(int length, UInt64 value)
        {
            var data = new byte[length];
            for (int i = 0; i < length; i++)
                data[i] = (byte)(value >> (length - 1 - i) * 8);
            WriteBytes(data);
        }

        public void WriteBytes(byte[] data)
        {
            Stream.Write(data, 0, data.Length);
        }
    }
}
